﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.Objects.DataClasses;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using NativeCode.Core.Data.LinqKit;
using NativeCode.Core.Extensions;

namespace NativeCode.Core.Data
{
	public enum QueryFilterConjunction
	{
		None = 0,
		And,
		Or,
	}

	public enum QueryFilterOperator
	{
		None = 0,
		Equals = 1,
		GreaterThan = 2,
		GreaterThanOrEqualTo = 3,
		LessThan = 4,
		LessThanOrEqualTo = 5,
		NotEquals = 6,
		Contains = 7,
		Between = 8,
	}

	public class QueryFilter
	{
		public QueryFilter(string key, string displayName, string propertyName, QueryFilterOperator queryFilterOperator,
		                   Expression filterExpression, Func<IEnumerable<object>> filterOptionsQuery, Type filterType)
		{
			Key = key;
			DisplayName = displayName.ToDisplayText();
			PropertyName = propertyName;
			FilterOperator = queryFilterOperator;
			FilterExpession = filterExpression;
			FilterOptionsQuery = filterOptionsQuery;
			FilterType = filterType;
		}

		[Description("Key")]
		public string Key { get; set; }

		[Description("Display")]
		public string DisplayName { get; set; }

		[Description("Property")]
		public string PropertyName { get; set; }

		[Description("Ignore Case")]
		public bool IgnoreCase { get; set; }

		[Description("Operator")]
		public QueryFilterOperator FilterOperator { get; set; }

		[Description("Expression")]
		public Expression FilterExpession { get; set; }

		[Description("Options")]
		public Func<IEnumerable<object>> FilterOptionsQuery { get; set; }

		[Description("Type")]
		public Type FilterType { get; set; }

		public QueryFilter SetFilterExpression<T, TV>(Expression<Func<T, TV, bool>> filterExpression)
		{
			FilterExpession = filterExpression;
			return this;
		}

		public QueryFilter SetFilterOptionsQuery(Func<IEnumerable<object>> filterOptionsQuery)
		{
			FilterOptionsQuery = filterOptionsQuery;
			return this;
		}

		#region Filtering

		/// <summary>
		/// Creates an expression in the form of:
		///		(T, value) => predicate.Invoke(T, value)
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="predicate"></param>
		/// <param name="value"></param>
		/// <returns></returns>
		protected Expression<Func<T, bool>> FilterByUserDefinedProperty<T>(Expression<Func<T, bool>> predicate, object value)
		{
			// Creates: (T, value) => 
			ParameterExpression paramCustomer = Expression.Parameter(typeof (T), PropertyName);
			ConstantExpression paramValue = Expression.Constant(value);

			// Creates: => delegate(T, value);
			InvocationExpression invocationExpression = Expression.Invoke(FilterExpession, paramCustomer, paramValue);
			return Expression.Lambda<Func<T, bool>>(invocationExpression, paramCustomer);
		}

		/// <summary>
		/// Passing in a null predicate will just return the expression so you have something to work with.
		/// Subsequent calls passing in a non-null predicate will chooses how to join the two statements with
		/// the filter junction.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="predicate"></param>
		/// <param name="value"></param>
		/// <param name="filterConjunction"></param>
		/// <returns></returns>
		public Expression<Func<T, bool>> ApplyFilter<T>(Expression<Func<T, bool>> predicate, object value, QueryFilterConjunction filterConjunction)
		{
			if (predicate == null)
			{
				return FilterByUserDefinedProperty(predicate, value);
			}

			// There is a difference here. First we check if the type that we are targetting inherits
			// from EntityObject or not. If it does, we're in a LINQ-to-Entities framework. If not, we
			// can safely use the default PredicateBuilder And and Or extensions.
			if (typeof(EntityObject).IsAssignableFrom(typeof(T)))
			{
				switch (filterConjunction)
				{
					case QueryFilterConjunction.And:
						return predicate.ComposeAnd(FilterByUserDefinedProperty(predicate, value));
					case QueryFilterConjunction.Or:
						return predicate.ComposeOr(FilterByUserDefinedProperty(predicate, value));
				}
			}
			else
			{
				switch (filterConjunction)
				{
					case QueryFilterConjunction.And:
						return predicate.And(FilterByUserDefinedProperty(predicate, value));
					case QueryFilterConjunction.Or:
						return predicate.Or(FilterByUserDefinedProperty(predicate, value));
				}
			}

			return predicate;
		}

		#endregion

		#region Helpers

		public override string ToString()
		{
			return string.Format("Key: {0}, DisplayName: {1}, PropertyName: {2}, Expression: {3}", Key, DisplayName, PropertyName, FilterExpession);
		}

		public string ToHtmlString()
		{
			StringBuilder results = new StringBuilder();

			Type type = typeof (QueryFilter);
			var properties = from property in type.GetProperties()
			                 let description = (DescriptionAttribute) property.GetCustomAttributes(typeof (DescriptionAttribute), false).FirstOrDefault()
			                 where description != null
			                 select new
			                 {
			                 	PropertyInfo = property,
			                 	DescriptionText = description.Description,
			                 };

			results.AppendLine("<ul>");
			foreach (var property in properties)
			{
				results.AppendLine(string.Format("<li><span><b>{0}:</b> {1}</span></li>", property.DescriptionText, property.PropertyInfo.GetValue(this, new object[] {})));
			}
			results.AppendLine("</ul>");

			return results.ToString();
		}

		public static string GetFilterKey(Type type, string propertyName, QueryFilterOperator queryFilterOperator)
		{
			return FilterableTypesRegistry.CreateFilterKey(type, propertyName, queryFilterOperator);
		}

		#endregion
	}
}